import java.util.*;
import java.math.*;

public class GenerateTriples extends Thread{
    
    Player p;
    HashMap<Integer, BigInteger[]> as, bs, rs;
    HashMap<Integer, BigInteger> cs, ds;
    BigInteger[] cs_t;
    HashMap<Integer, MessageList> msgs;

    int count = 0;

    public GenerateTriples(Player p, HashMap<Integer, MessageList> msgs){        
        this.p = p;
        this.msgs = msgs;
        as = new HashMap<Integer, BigInteger[]>(); //[0] contains 'x' shared w. degree t. 
        bs = new HashMap<Integer, BigInteger[]>(); //[1] contains 'x' shared w. degree t'
        rs = new HashMap<Integer, BigInteger[]>(); //same, except [1] is 2t'
        cs = new HashMap<Integer, BigInteger>(); // c shared with degree 2t'
        ds = new HashMap<Integer, BigInteger>(); // d shared with degree 2t'
        cs_t = new BigInteger[Utilities.T]; // c shared with degree t.
    }

    public void run(){
        if(count == 2){
            //this is for [r_i]
            p.doubleShareRandom(Utilities.t, Utilities.t_prime*2, msgs);
        }
        else{
            //this is for [a_i] and [b_i]
            p.doubleShareRandom(Utilities.t, Utilities.t_prime, msgs);
        }
            
        while(p.getRandomDoubleShares() == null){
            //wait for the player to get the random shares needed.
            yield();
        }
        switch(count){
        case 0:
            as = p.getRandomDoubleShares();
            break;
        case 1: 
            bs = p.getRandomDoubleShares();
            break;
        case 2:
            rs = p.getRandomDoubleShares();
            break;
        }
        p.setRandomDoubleShares(null);
        count++;
        /*
        try{
            //in order not to interfere with the DoubleShareRandom
            //Protocol, we need to sleep for a small amount of time.
            //TODO: This is not a good solution I think.. Does not scale well. 
            sleep(100);
        }
        catch(InterruptedException e){
            e.printStackTrace();
        }
        */
        if(count < 3){
            //only want to get 3 double-shares.
            run();
        }        
        else{
            System.out.println("Done generating double-shares");
            generateTriplesStep2();
        }
    }
    
    public void generateTriplesStep2(){
        //compute locally [c_k]_{2t'}:  [2.1]
        for(Integer i: as.keySet()){
            BigInteger[] tmp1 = new BigInteger[2];
            BigInteger[] tmp2 = new BigInteger[2];
            tmp1 = as.get(i);
            tmp2 = bs.get(i);
            BigInteger c_k_2t_prime = tmp1[1].multiply(tmp2[1]);
            cs.put(i, c_k_2t_prime.mod(Utilities.F_big));
            //debugging...
            /*
                System.out.println("");
                System.out.println("Player " + p.ID + " has a share of c_K_2t being: " + cs.get(i));
                System.out.println("Player " + p.ID + " has a share of a_t' being: " + as.get(i)[1]);
                System.out.println("Player " + p.ID + " has a share of a_t being: " + as.get(i)[0]);
                System.out.println("Player " + p.ID + " has a share of b_t' being: " + bs.get(i)[1]);
                System.out.println("");
            */
        }

        //compute [d_k]_{2t'} :  [2.2]
        for(Integer i: cs.keySet()){
            BigInteger c_k = cs.get(i);
            BigInteger r_k = (rs.get(i))[1];
            BigInteger d_k = c_k.subtract(r_k);            
            ds.put(i,d_k.mod(Utilities.F_big));
        }

        //utilize ReconsPubl in order to reconstruct d_1,...,d_T
        //towards all players in P':  [2.3]
        p.reconsPubl(2*Utilities.t_prime, ds, Utilities.GENTRIP, msgs);
    }
    
    /**
     * printing method for debugging purposes.
     */
    public void notice(){
        Utilities.lock.lock();
        print("as, bs, rs, cs and ds should be filled out now. Printing everything: === I am player " + p.getID());
        print_less("[");
        for(Integer i : as.keySet()){
            BigInteger[] results = as.get(i);
            print_less("("+results[0]+", " + results[1] + "), ");
        }
        print("]");
        print_less("[");
        for(Integer i : bs.keySet()){
            BigInteger[] results = bs.get(i);
            print_less("("+results[0]+", " + results[1] + "), ");
        }
        print("]");
        print_less("[");
        for(Integer i : rs.keySet()){
            BigInteger[] results = rs.get(i);
            print_less("("+results[0]+", " + results[1] + "), ");
        }
        print("]");
        print_less("[");
        for(Integer i : cs.keySet()){
            BigInteger result = cs.get(i);
            print_less(result + ", ");
        }
        print("]");
        print_less("[");
        for(Integer i : ds.keySet()){
            BigInteger result = ds.get(i);
            print_less(result + ", ");
        }
        print("]");
        Utilities.lock.unlock();
    }

    /**
     * @returns an ArrayList consisting of T triples of the form ([a],[b],[c]).
     */
    public ArrayList<BigInteger[]> lastStep(BigInteger[] reconstructedDs){
        if(reconstructedDs.length < Utilities.T){
            //Error - we got less reconstructed values than we need.
            print("ERROR - got less reconstructed values than we need.");
            return null;
        }

        int k = 0;
        for(Integer i : rs.keySet()){
            //[c_k]_t = [r_k]_t + d_k  (same as [d_k]_0)
            BigInteger multResult = rs.get(i)[0].add(reconstructedDs[k]).mod(Utilities.F_big);
            cs_t[k] = multResult;
            k++;
        }
        ArrayList<BigInteger[]> triples = new ArrayList<BigInteger[]>();

        for(int i = 0; i < Utilities.T; i++){
            BigInteger[] triple = new BigInteger[3];
            triple[0] = as.get(i+1)[0];
            triple[1] = bs.get(i+1)[0];
            triple[2] = cs_t[i];
            triples.add(triple);
        }
        return triples;
    }


    public void print(String msg){
        if(Utilities.DEBUG){
            System.out.println(msg);
        }
    }
    
    public void print_less(String msg){
        if(Utilities.DEBUG){
            System.out.print(msg);
        }
    }

}